<?php
/**
 *
 *
 */
abstract class Yz_Module_Abstract
    extends Yz_Class
    implements Yz_Module_Interface
{

    protected static $instance;
    /**
     *  @var Yz_Config_Interface&
     */
    protected $config;
    /**
     *  @var String
     */
    protected $name;
    /**
     *  @var String
     */
    protected $path;
    /**
     *  @var String
     */
    protected $namespaceName;
    /**
     *  @var Yz_I18N_Interface
     */
    protected $i18n;



    /** Singleton by default
     *  @return Yz_Module_Interface&
     */
    public static function getInstance()
    {
        debug();
        $i =& static::$instance;
        if (null === $i) {
             $i = new static();
        }
        return $i;
    }

    /**
     *
     */
    protected function __construct($selector=null)
    {
        debug();
        $ns = $this->namespaceName();
        $this->namespaceName = $ns; // referenced too often
        $this->path = strtr($ns, '\\', '/') . '/';
        $nsx = explode('\\', $ns);
        $this->name = $nsx[0] . '_' . $nsx[2];

        // setup PARENT_NAMESPACE
        $parentClass = get_parent_class($this);
        while ("" !== $ns) {
            $const = $ns . '\\PARENT_NAMESPACE';
            if ( defined($const) ) {
                break;
            }
            $ns = $parentClass::namespaceName();
            define($const,  $ns);
            $parentClass = get_parent_class($parentClass);
        }
        return;
    }

    /**
     *  @return Yz_Config_Interface&
     *  @todo include config based on module selector
     */
    public function getConfig()
    {
        if (null === $this->config) {
            // file
            $file = new Yz_File( $this->path . 'config' );
            $str = $file->getContents();
            $src = $this->name;
            if ( null === $str ) {
                $config = null;
                //throw new Yz_Exception("Config not found for module");
            } else {
                $config = Yz_Config_Loader::load(
                    $str,
                    $src
                );
            }
            // init config
            $this->config = new Yz_Config($config, $src);
        }
        return $this->config;
    }

    /**
     *  @return string: `name' as "Error" in "Yz_Module_Error"
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     *  @return string: `path' as "Yz/modules/Error/" in "Yz_modules_Error"
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     *  @return Yz_I18N_Interface
     */
    public function getI18N()
    {
        $i18n =& $this->i18n;
        if (null === $i18n) {
            $i18nClass = $this->resolve('I18N_Default');
            $i18n = new $i18nClass(null);
        }
        return $i18n;
    }

    /**
     *  @param string|null $model
     *  @return Yz_Model_Interface
     */
    public function selectModel($name=null)
    {
        debug();
        $name = (null === $name
            ? 'Default'
            : ucfirst(Yz_String::identifier($name))
            );
        $class = $this->resolve('Model_' . $name);
        assert('is_subclass_of($class,"Yz_Model_Interface")');
        return new $class($this);
    }



    /** resolve a class name to a real one, in the module's namespace
     *  @param string className eg. "Controller_Default", without namespace
     *  @return string|false : the real class name on success, false on failure
     *  @throw InvalidParameter if class name is not a string
     */
    public static function resolveClass($className)
    {
        debug();
        $className = Yz_String::testNotEmpty($className);
        return static::resolve($className);
    } // resolveClass


    /** resolve a library given its `type' and `name'
     *  @param string type eg. "Controller"
     *  @param string name eg. "Default"
     *  @return Object|false library instance, initialized with the module's instance
     *  @throw InvalidParameter if class name is not a string
     */
    public static function resolveLibrary($type, $name)
    {
        debug();
        $type = ucfirst(Yz_String::identifier($type));
        $name = ucfirst(Yz_String::identifier($name));
        $className = $type . '_' . $name;
        return static::resolve($className);
    } // resolveLibrary



    /** resolve a virtual class name to a real one, as if it were a virtual method of the module
     *  @param string className eg. "Controller_Default"
     *  @return string|false : the real class name on success
     *  TODO honor variables static::class_<className>
     */
    protected static function resolve($className, $throwOnFail=true)
    {
        $ns = static::namespaceName();
        $myns = $ns;
        debug("{$ns}: Resolving '$className'");
        $found = false;
        while ("" !== $ns) {
            $tryName = $ns . '\\' . $className;
            debug("Trying classname '$tryName'...");
            $found = Yz::loadClass($tryName);

            if (false !== $found) {
                break;
            }
            $found = self::classByConst($ns, $className);
            if (false !== $found) {
                break;
            }
            $ns = constant($ns . '\\PARENT_NAMESPACE');
        }

        if (false === $found) {
            debug("Class '$className' could not be resolved.");
            if ($throwOnFail) {
                $ex = new Yz_Module_Exception_ClassNotFound();
                throw $ex->withArgs(array(
                    'moduleName' => static::className(),
                    'className' => $className,
                    ))
                    ;
            }
            return false;
        }

        debug("Class '$className' resolved to '$found'.");
        // must be a subclass of Yz_Class
        assert( 'is_subclass_of($found, "Yz_Class")' );
        $found = $found::virtual($myns, $className);
        return $found;
    }


    protected static function classByConst($ns, $className)
    {
        $const = $ns . '\\' . $className;
        if (defined($const)) {
            $class = constant($const);
            debug("Class '{$class}'");
            $found = Yz::loadClass($class);
            if (false === $found) {
                $ex = new Yz_Exception_ClassNotFound();
                throw $ex->withArgs(array(
                    'className' => $class,
                ))
                ;
            }
            return $class;
        }
        return false;
    }

} // class
